package org.suze.springboot.elasticsearch.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.PrefixQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.suze.springboot.elasticsearch.service.ElasticSearchService;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @description: ElasticSearchController
 * @Date : 2020/5/14 下午3:18
 * @Author : 石冬冬-Seig Heil
 */
@RestController
@Api(description = "elasticSearch", tags = "elasticSearch")
@RequestMapping("/elasticSearch")
@Slf4j
public class ElasticSearchController {

    @Autowired
    RestHighLevelClient client;

    @Autowired
    ElasticSearchService elasticSearchService;

    @ApiOperation(value = "createIndex", notes = "createIndex")
    @PostMapping("/createIndex/{indexName}")
    public Result createIndex(@PathVariable String indexName, @RequestBody Map<String,Map<String,Object>> config) {
        try {
            CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
            createIndexRequest.settings(config.get("settings"));
            createIndexRequest.mapping(config.get("mapping"));
            CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            return Result.suc(JSONObject.toJSON(createIndexResponse));
        } catch (IOException e) {
            log.error("[createIndex]",e);
            return Result.fail(e);
        }
    }

    @ApiOperation(value = "indexExists", notes = "indexExists")
    @GetMapping("/indexExists/{indexName}")
    public Result indexExists(@PathVariable String indexName) {
        return Result.suc(elasticSearchService.exists(indexName));
    }

    @ApiOperation(value = "es测试删除索引接口", notes = "es测试删除索引接口")
    @GetMapping("/deleteIndex/{indexName}")
    public Result deleteIndex(@PathVariable String indexName) {
        return Result.suc(elasticSearchService.deleteIndex(indexName));
    }

    @ApiOperation(value = "insertRecord", notes = "insertRecord")
    @PostMapping("/insertRecord/{indexName}")
    public Result insertRecord(@RequestBody Map<String,Object> param, @PathVariable String indexName) {
        IndexRequest indexRequest = new IndexRequest(indexName);
        String userJson = JSONObject.toJSONString(param);
        indexRequest.source(userJson, XContentType.JSON);
        try {
            IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);
            if (response != null) {
                ReplicationResponse.ShardInfo shardInfo = response.getShardInfo();
                if (shardInfo.getTotal() != shardInfo.getSuccessful()) {
                    log.info("shardInfo={}", JSONObject.toJSON(shardInfo));
                }
                // 如果有分片副本失败，可以获得失败原因信息
                if (shardInfo.getFailed() > 0) {
                    for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {
                        String reason = failure.reason();
                        log.info("副本失败原因,reason={}",reason);
                    }
                }
            }
            return Result.suc(JSONObject.toJSON(response));
        } catch (IOException e) {
            log.info("[insertRecord]",e);
            return Result.fail(e);
        }
    }

    @ApiOperation(value = "bulkInsertRecord", notes = "bulkInsertRecord")
    @PostMapping("/bulkInsertRecord/{indexName}")
    public Result bulkInsertRecord(@RequestBody List<Map<String,Object>> batch, @PathVariable String indexName) {
        BulkRequest bulkRequest = new BulkRequest();
        batch.forEach(each -> {
            IndexRequest indexRequest = new IndexRequest(indexName).source(each, XContentType.JSON);
            bulkRequest.add(indexRequest);
        });
        //同步
        try {
            BulkResponse response = client.bulk(bulkRequest,RequestOptions.DEFAULT);
            return Result.suc(JSONObject.toJSON(response));
        } catch (IOException e) {
            log.info("[updateRecord]",e);
            return Result.fail(e);
        }
    }


    @ApiOperation(value = "updateRecord", notes = "updateRecord")
    @PostMapping("/updateRecord/{indexName}")
    public Result updateRecord(@PathVariable String indexName, @RequestParam String id, @RequestBody Map<String,Object> param) {
        UpdateRequest updateRequest = new UpdateRequest(indexName,id);
        updateRequest.doc(param);
        try {
            UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
            return Result.suc(JSONObject.toJSON(response));
        } catch (IOException e) {
            log.info("[updateRecord]",e);
            return Result.fail(e);
        }
    }

    @ApiOperation(value = "deleteRecord", notes = "deleteRecord")
    @GetMapping("deleteRecord/{indexName}")
    public Result deleteRecord(@RequestParam String id, @PathVariable String indexName) {
        DeleteRequest deleteRequest = new DeleteRequest(indexName);
        deleteRequest.id(id);
        try {
            DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT);
            return Result.suc(JSONObject.toJSON(response));
        } catch (IOException e) {
            log.info("[updateRecord]",e);
            return Result.fail(e);
        }
    }


    @ApiOperation(value = "queryRecord", notes = "queryRecord")
    @GetMapping("/queryRecord/{indexName}")
    public Result queryRecord(@PathVariable String indexName) {
        SearchRequest searchRequest = new SearchRequest(indexName);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //如果用name直接查询，其实是匹配name分词过后的索引查到的记录(倒排索引)；如果用name.keyword查询则是不分词的查询，正常查询到的记录
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("birthday").from("1991-01-01").to("2010-10-10").format("yyyy-MM-dd");//范围查询
//        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name.keyword", name);//精准查询
        PrefixQueryBuilder prefixQueryBuilder = QueryBuilders.prefixQuery("name.keyword", "张");//前缀查询
//        WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("name.keyword", "*三");//通配符查询
//        FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "三");//模糊查询
        FieldSortBuilder fieldSortBuilder = SortBuilders.fieldSort("age");//按照年龄排序
        fieldSortBuilder.sortMode(SortMode.MIN);//从小到大排序

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(rangeQueryBuilder).should(prefixQueryBuilder);//and or  查询

        sourceBuilder.query(boolQueryBuilder).sort(fieldSortBuilder);//多条件查询
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(sourceBuilder);
        try {
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            JSONArray jsonArray = new JSONArray();
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                JSONObject jsonObject = JSON.parseObject(sourceAsString);
                jsonArray.add(jsonObject);
            }
            return Result.suc();
        } catch (IOException e) {
            e.printStackTrace();
            return Result.suc();
        }
    }

    @ApiOperation(value = "queryAggRecord", notes = "queryAggRecord")
    @GetMapping("/queryAggRecord/{indexName}")
    public Result queryAggRecord(@PathVariable String indexName) {
        SearchRequest searchRequest = new SearchRequest(indexName);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("by_age").field("age");
        sourceBuilder.aggregation(termsAggregationBuilder);

        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(sourceBuilder);

        try {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            Aggregations aggregations = searchResponse.getAggregations();
            Map<String, Aggregation> stringAggregationMap = aggregations.asMap();
            ParsedLongTerms parsedLongTerms = (ParsedLongTerms) stringAggregationMap.get("by_age");
            List<? extends Terms.Bucket> buckets = parsedLongTerms.getBuckets();
            Map<Integer, Long> map = new HashMap<>();
            for (Terms.Bucket bucket : buckets) {
                //个数
                long docCount = bucket.getDocCount();
                //年龄
                Number keyAsNumber = bucket.getKeyAsNumber();
                System.err.println(keyAsNumber + "岁的有" + docCount + "个");
                map.put(keyAsNumber.intValue(), docCount);
            }
            return Result.suc();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }



    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    static class Result<E>{

        private final static int SUCCESS_CODE = 0;
        private final static int FAILURE_CODE = 1;
        private final static String SUCCESS_MSG = "操作成功";
        private final static String FAILURE_MSG = "操作失败";
        private static final long serialVersionUID = -6237151417035547947L;
        /**
         * 是否执行成功
         */
        private boolean success;
        /**
         * 包体
         */
        private E data;
        /**
         * 执行操作code
         */
        private int code;
        /**
         * 业务消息
         */
        private String msg;

        /**
         * 静态方法，返回执行成功
         * @return
         */
        public static Result suc(){
            return Result.builder().success(true).data(SUCCESS_MSG).code(SUCCESS_CODE).build();
        }

        /**
         * 静态方法，返回执行成功
         * @param t
         * @param <E>
         * @return
         */
        public static <E> Result suc(E t){
            return Result.builder().success(true).data(t).code(SUCCESS_CODE).build();
        }

        /**
         * 静态方法，返回执行失败
         * @param <E>
         * @return
         */
        public static <E> Result fail(){
            return Result.builder().success(false).data(FAILURE_MSG).code(FAILURE_CODE).build();
        }

        /**
         * 静态方法，返回执行失败
         * @param t
         * @param <E>
         * @return
         */
        public static <E> Result fail(E t){
            return Result.builder().success(false).data(t).code(FAILURE_CODE).build();
        }
    }

    @Data
    static class QueryForm {

        private Integer id;

        private String age;

        private String name;

        private String address;

        private String birthday;

        private String birthdayRange;

        private String ageRange;


        SearchSourceBuilder searchSourceBuilder(){
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            if(id != null){
                sourceBuilder.query(QueryBuilders.termQuery("id", id));
            }
            if(name != null){
                sourceBuilder.query(QueryBuilders.termQuery("name", id));
            }
            if(age != null){
                sourceBuilder.query(QueryBuilders.termQuery("age", id));
            }
            if(address != null){
                sourceBuilder.query(QueryBuilders.termQuery("address", id));
            }
            if(birthday != null){
                sourceBuilder.query(QueryBuilders.termQuery("birthday", id));
            }
            if(birthdayRange != null){
                String[] array = birthdayRange.split("-");
                sourceBuilder.query(QueryBuilders.boolQuery().must(QueryBuilders.rangeQuery("birthday").from(array[0]).to(array[1]).format("yyyy-MM-dd")));
            }
            if(ageRange != null){
                String[] array = ageRange.split("-");
                sourceBuilder.query(QueryBuilders.boolQuery().must(QueryBuilders.rangeQuery("age").from(array[0]).to(array[1])));
            }
            return sourceBuilder;
        }
    }
}
